﻿namespace CacheProvider.AppFabric
{
    #region N A M E S P A C E   I M P O R T S

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Configuration;
    using System.Diagnostics;
    using System.Linq;
    using System.Reflection;
    using System.Threading;
    using CacheProviderEntities;
    using CacheProviderInterfaces;
    using Microsoft.ApplicationServer.Caching;
    using Microsoft.Practices.Composite.Presentation.Events;

    #endregion

    public class AppFabricCacheProvider : CacheProvider
    {
        #region P R I V A T E   A T T R I B U T E S

        private string _name = "CacheProvider.AppFabricCache";

        private string _description = "Cache Provider which wraps Microsoft.ApplicationServer.Caching(AppFabric)";

        //Dictionary to hold ThreadId and its corresponding NamedCache value
        private Dictionary<int, string> _namedCacheThreadTable;

        private static readonly ReaderWriterLockSlim NamedCacheThreadTableLocker = new ReaderWriterLockSlim();

        private IDataCacheFactoryPool _dataCacheFactoryPool;

        private List<CacheLockEntry> _cacheLockTable;

        private static readonly ReaderWriterLockSlim CacheLockTableLocker = new ReaderWriterLockSlim();

        private IDependencyMonitoringManager _sqlMonitor;

        private Int16 _dataCacheFactoryPoolSize;
        private bool _eagerPoolingEnabled;

        private string _defaultNamedCache;

        private static readonly object Serializer = new object();

        #endregion

        #region P R O V I D E R B A S E   M E M B E R   O V E R R I D E S

        public override string Name
        {
            get
            {
                return _name;
            }
        }

        public override string Description
        {
            get
            {
                return _description;
            }
        }

        public override void Initialize(string name, NameValueCollection config)
        {
            if (null == config)
            {
                throw (new ArgumentNullException("config"));
            }

            if (string.IsNullOrEmpty(name))
            {
                name = this._name;
            }
            else
            {
                this._name = name;
            }

            if (string.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", _description);
            }
            else
            {
                this._description = config["description"];
            }

            // Call the base class implementation.
            base.Initialize(name, config);

            // Load configuration data.            
            this.DefaultExpirationTimeInMilliSeconds =
                Convert.ToInt64(config["defaultExpirationTimeInMS"] ?? "60000");

            this._dataCacheFactoryPoolSize = Convert.ToInt16(config["dataCacheFactoryPoolSize"] ?? "1");

            this._eagerPoolingEnabled = Convert.ToBoolean(config["eagerPooling"] ?? "false");

            this._defaultNamedCache = config["defaultNamedCache"];

            //this.Database = (Database)Enum.Parse(typeof(Database), config["database"] ?? "SqlServer", true);

            //Forming parameters for DataCacheFactoryPool ctor
            var datacacheFactoryPoolCtorParams = new Hashtable
                                                     {
                                                         {
                                                             "maxPoolSize", this.DataCacheFactoryPoolSize
                                                             },
                                                         {
                                                             "eagerLoad",
                                                             this.EagerPoolingEnabled
                                                             }, {
                                                                    "logWriter",
                                                                    this.LogWriter
                                                                    }
                                                     };

            this._dataCacheFactoryPool = this.DIContainer.Resolve<IDataCacheFactoryPool>(datacacheFactoryPoolCtorParams);

            Debug.Assert(this._dataCacheFactoryPool != null, "this._dataCacheFactoryPool instance must be set here!");

            this._cacheLockTable = new List<CacheLockEntry>();
            this._namedCacheThreadTable = new Dictionary<int, string>();

            var monitoringMgrCtorParams = new Hashtable { { "cacheProvider", this } };

            switch (this.Database)
            {
                case Database.SqlServer:
                    this._sqlMonitor = this.DIContainer.Resolve<IDependencyMonitoringManager>("SqlMonitoringManager", monitoringMgrCtorParams);
                    break;
                case Database.Oracle:
                    this._sqlMonitor = this.DIContainer.Resolve<IDependencyMonitoringManager>("OracleMonitoringManager", monitoringMgrCtorParams);
                    break;
            }
            
            Debug.Assert(this._sqlMonitor != null, "SQLMonitoringManager instance must be set here!");

            if (EagerPoolingEnabled && this._dataCacheFactoryPool.PoolSize > 0)
                this.UpdateDependencyTrackerCollection();
        }

        #endregion

        #region P R O P E R T I E S

        public override long DefaultExpirationTimeInMilliSeconds { get; set; }

        public override Int16 DataCacheFactoryPoolSize { get { return this._dataCacheFactoryPoolSize; } }

        public override bool EagerPoolingEnabled { get { return this._eagerPoolingEnabled; } }

        public override bool IsCacheTagSupported
        {
            get
            {
                return true;
            }
        }

        public override bool IsRegionSupported
        {
            get
            {
                return true;
            }
        }

        public override bool IsConcurrencySupported
        {
            get
            {
                return true;
            }
        }

        public override bool IsNamedCacheSupported
        {
            get
            {
                return true;
            }
        }

        public override string CacheName
        {
            get
            {

                try
                {
                    //If the named cache is present in the dictionary,return the same else return null.
                    NamedCacheThreadTableLocker.EnterReadLock();

                    var currentThreadId = Thread.CurrentThread.ManagedThreadId;
                    string cacheName = null;

                    //If the User Has has opted for a Named Cache At A Thread Level, return the same /// <see cref="SetNamedCacheImpl"/>
                    //Else, if a Named Cache is Defined At Application Level, return the same
                    //Else return null.
                    if (this._namedCacheThreadTable.ContainsKey(currentThreadId))
                        cacheName = this._namedCacheThreadTable[currentThreadId];
                    else if (!string.IsNullOrEmpty(this._defaultNamedCache))
                        cacheName = this._defaultNamedCache;

                    return cacheName;

                    //return this._namedCacheThreadTable.ContainsKey(currentThreadId)
                    //        ? this._namedCacheThreadTable[currentThreadId]
                    //        : null;
                }
                finally
                {
                    NamedCacheThreadTableLocker.ExitReadLock();
                }
            }
        }

        public override bool IsCacheItemVersioningSupported
        {
            get { return true; }
        }

        #endregion

        private bool _IsCacheAccessible = true;

        public bool CacheAccessible
        {
            get
            {
                lock (Serializer)
                    return _IsCacheAccessible;
            }
            set
            {
                lock (Serializer)
                    _IsCacheAccessible = value;
            }
        }


        #region A D D   O V E R L O A D S

        /// <summary>
        /// Adds an object to the cache.
        /// If an item using the same key is already present in the cache, this call throws an exception of type 
        /// <see cref="DataCacheException"/> with the ErrorCode set to KeyAlreadyExists.
        /// Expiration settings are derived from the named\default cache configuration
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value">Any .NET object that is Serializable</param>
        /// <returns></returns>
        public override bool Add(string key, object value)
        {
            bool result = false;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                dataCache.Add(key, value);
                result = true;
            }

            return result;
        }

        public override bool Add(string key, object value, TimeSpan timeout)
        {
            bool result = false;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                dataCache.Add(key, value, timeout);
                result = true;
            }

            return result;
        }

        /// <summary>
        /// Adds an object to the cache.
        /// If an item using the same key is already present in the cache, this call throws an exception of type 
        /// <see cref="DataCacheException"/> with the ErrorCode set to KeyAlreadyExists.
        /// Expiration settings are derived from the named cache configuration
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value">Any .NET object that is Serializable</param>
        /// <param name="useDefaultExpiration">Will use the default Expiration time configured
        /// through provider configuration. Must be set to True when using this overload.  
        /// Else use other Add Overloads.</param>
        /// <returns>True if Added successfully else False</returns>
        public override bool Add(string key, object value, bool useDefaultExpiration)
        {
            if (useDefaultExpiration == false)
                throw new InvalidOperationException(@"Parameter: useDefaultExpiration must be set to true when using this overload.  Else, use Add(string key, object value, long ttlInMilliSeconds)");

            bool result = this.Add(key, value, this.DefaultExpirationTimeInMilliSeconds);
            return result;
        }

        /// <summary>
        /// Adds an object to the cache.
        /// If an item using the same key is already present in the cache, this call throws an exception of type 
        /// <see cref="DataCacheException"/> with the ErrorCode set to KeyAlreadyExists.
        /// Expiration settings are derived from the named cache configuration
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value">Any .NET object that is Serializable</param>
        /// <param name="ttlInMilliSeconds">Expiration time in milliseconds</param>
        /// <returns></returns>
        public override bool Add(string key, object value, long ttlInMilliSeconds)
        {
            bool result = false;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                dataCache.Add(key, value, TimeSpan.FromMilliseconds(ttlInMilliSeconds));
                result = true;
            }
            return result;
        }

        /// <summary>
        /// Adds an object to the cache.
        /// If an item using the same key is already present in the cache, this call throws an exception of type 
        /// <see cref="DataCacheException"/> with the ErrorCode set to KeyAlreadyExists.
        /// Expiration settings are derived from the named cache configuration
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="cacheTags">Tags that can be associated with the object to be Cached.</param>
        /// <returns>True if successful else false</returns>
        /// <remarks>
        /// Cache Tags are supported only for objects that are stored in a region.  
        /// Hence, Retrieving by Tags is not possible for objects stored without a region but with Cache Tags.
        /// </remarks>
        protected override bool AddImpl(string key, object value, IEnumerable<string> cacheTags)
        {
            var result = false;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                var dataCacheTags = cacheTags.Select(cacheTag => new DataCacheTag(cacheTag));

                if (dataCacheTags.Count() > 0)
                    dataCache.Add(key, value, dataCacheTags);
                else
                    dataCache.Add(key, value);

                result = true;
            }
            return result;
        }

        protected override bool AddImpl(string key, object value, IEnumerable<string> cacheTags, string regionName)
        {
            bool result = false;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                var dataCacheTags = cacheTags.Select(cacheTag => new DataCacheTag(cacheTag));
                dataCache.Add(key, value, dataCacheTags, regionName);
                result = true;
            }
            return result;
        }

        protected override bool AddImpl(string key, object value, string regionName)
        {
            var result = false;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                if (!string.IsNullOrEmpty(regionName))
                    dataCache.Add(key, value, regionName);
                else
                    dataCache.Add(key, value);

                result = true;
            }
            return result;
        }

        protected override bool AddImpl(string key, object value, TimeSpan timeout, IEnumerable<string> cacheTags)
        {
            var result = false;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                var dataCacheTags = cacheTags.Select(cacheTag => new DataCacheTag(cacheTag));
                if (dataCacheTags.Count() > 0)
                    dataCache.Add(key, value, timeout, dataCacheTags);
                else
                    dataCache.Add(key, value, timeout);

                result = true;
            }
            return result;
        }

        protected override bool AddImpl(string key, object value, TimeSpan timeout, IEnumerable<string> cacheTags, string regionName)
        {
            if (string.IsNullOrEmpty(regionName))
                throw new ArgumentException("Tags may only be used to retrieve a cached object if that object is stored in a region. Hence,parameter regionName must have a value");


            var result = false;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                var dataCacheTags = cacheTags.Select(cacheTag => new DataCacheTag(cacheTag));

                dataCache.Add(key, value, timeout, dataCacheTags, regionName);
                result = true;
            }
            return result;
        }

        protected override bool AddImpl(string key, object value, TimeSpan timeout, string regionName)
        {
            if (string.IsNullOrEmpty(regionName))
                throw new ArgumentNullException("regionName");

            bool result = false;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                dataCache.Add(key, value, timeout, regionName);
                result = true;
            }
            return result;
        }

        #endregion

        #region P U T   O V E R L O A D S

        public override bool Put(string key, object value)
        {
            if (!this.CacheAccessible)
                return false;
            var result = false;
            if (PerformPut(key, value) != null)
                result = true;
            return result;
        }

        public override bool Put(string key, object value, bool useDefaultTimeout)
        {
            if (!this.CacheAccessible)
                return false;

            if (!useDefaultTimeout)
                throw new InvalidOperationException("parameter [useDefaultTimeout] must be true for this Put() Overload");

            var result = false;
            var timeout = TimeSpan.FromMilliseconds(this.DefaultExpirationTimeInMilliSeconds);
            if (PerformPut(key, value, timeout) != null)
                result = true;

            return result;

        }

        private DataCacheItemVersion PerformPut(string key, object value)
        {
            DataCacheItemVersion result = null;
            var shouldRetry = false;
            var retryCount = 1;
            do
            {
                try
                {
                    if (!this.CacheAccessible)
                        return result;

                    var dataCache = this.GetDataCache(retryCount > 1);
                    if (dataCache != null)
                    {
                        result = dataCache.Put(key, value);
                    }
                }
                catch (DataCacheException dataCacheException)
                {
                    shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                    retryCount++;

                    //if (shouldRetry == false)
                    //    throw;
                }
            } while (shouldRetry);

            return result;
        }

        public override bool Put(string key, object value, TimeSpan timeout)
        {
            if (!this.CacheAccessible)
                return false;

            var result = false;
            if (PerformPut(key, value, timeout) != null)
                result = true;
            return result;
        }

        private DataCacheItemVersion PerformPut(string key, object value, TimeSpan timeout)
        {
            DataCacheItemVersion result = null;
            var retryCount = 1;
            var shouldRetry = false;
            do
            {
                try
                {
                    if (!this.CacheAccessible)
                        return result;

                    var dataCache = this.GetDataCache(retryCount > 1);
                    if (dataCache != null)
                    {
                        result = dataCache.Put(key, value, timeout);
                        var message = string.Format("Cache with key {0} {1} to namedCache {2}", key,
                                                        (result != null) ? "added" : "not added", this.CacheName);
                        if (result != null)
                            this.LogWriter.Write(message, this.LoggingCategory, 1, (int)LoggingEvent.CachePut,
                                                    TraceEventType.Information, key);
                        else
                            this.LogWriter.Write(message, this.LoggingCategory, 1, (int)LoggingEvent.CachePut,
                                                    TraceEventType.Error, key);
                    }
                }
                catch (DataCacheException dataCacheException)
                {
                    shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                    retryCount++;
                }
            } while (shouldRetry);

            return result;
        }

        protected override bool PutImpl(string key, object value, TimeSpan timeout, string regionName)
        {
            if (!this.CacheAccessible)
                return false;

            var result = false;

            if (this.PerformPut(key, value, timeout, regionName) != null)
            {
                result = true;
            }

            return result;
        }

        private DataCacheItemVersion PerformPut(string key, object value, TimeSpan timeout, string regionName)
        {
            DataCacheItemVersion result = null;
            var retryCount = 1;
            var shouldRetry = false;

            do
            {
                try
                {
                    if (!this.CacheAccessible)
                        return result;

                    var dataCache = this.GetDataCache(retryCount > 1);
                    if (dataCache != null)
                    {
                        result = dataCache.Put(key, value, timeout, regionName);
                    }
                }
                catch (DataCacheException dataCacheException)
                {
                    shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                    retryCount++;
                    //if (shouldRetry == false)
                    //    throw;
                }
            } while (shouldRetry);

            return result;
        }

        protected override bool PutImpl(string key, object value, TimeSpan timeout, IEnumerable<string> cacheTags, string regionName)
        {
            if (!this.CacheAccessible)
                return false;

            bool result = false;
            if (this.PerformPut(key, value, timeout, cacheTags, regionName) != null)
                result = true;
            return result;
        }

        private DataCacheItemVersion PerformPut(string key, object value, TimeSpan timeout, IEnumerable<string> cacheTags, string regionName)
        {
            DataCacheItemVersion result = null;
            var retryCount = 1;
            var shouldRetry = false;
            do
            {
                try
                {
                    if (!this.CacheAccessible)
                        return result;

                    var dataCache = this.GetDataCache(retryCount > 1);
                    if (dataCache != null)
                    {
                        var dataCacheTags = cacheTags.Select(cacheTag => new DataCacheTag(cacheTag));

                        result = dataCache.Put(key, value, timeout, dataCacheTags, regionName);
                    }
                }
                catch (DataCacheException dataCacheException)
                {
                    shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                    retryCount++;
                    //if (shouldRetry == false)
                    //    throw;
                }
            } while (shouldRetry);

            return result;
        }

        protected override bool PutImpl(string key, object value, TimeSpan timeout, IEnumerable<string> cacheTags)
        {
            if (!this.CacheAccessible)
                return false;

            bool result = false;
            if (PerformPut(key, value, timeout, cacheTags) != null)
                result = true;
            return result;
        }

        private DataCacheItemVersion PerformPut(string key, object value, TimeSpan timeout, IEnumerable<string> cacheTags)
        {
            DataCacheItemVersion result = null;
            var retryCount = 1;
            var shouldRetry = false;
            do
            {
                try
                {
                    if (!this.CacheAccessible)
                        return result;

                    var dataCache = this.GetDataCache(retryCount > 1);
                    if (dataCache != null)
                    {
                        var dataCacheTags = cacheTags.Select(cacheTag => new DataCacheTag(cacheTag));

                        result = dataCache.Put(key, value, timeout, dataCacheTags);
                    }
                }
                catch (DataCacheException dataCacheException)
                {
                    shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                    retryCount++;
                    //if (shouldRetry == false)
                    //    throw;
                }
            } while (shouldRetry);

            return result;
        }

        protected override bool PutImpl(string key, object value, string regionName)
        {
            if (!this.CacheAccessible)
                return false;

            bool result = false;
            if (this.PerformPut(key, value, regionName) != null)
                result = true;

            return result;
        }

        private DataCacheItemVersion PerformPut(string key, object value, string regionName)
        {
            DataCacheItemVersion result = null;
            var retryCount = 1;
            var shouldRetry = false;
            do
            {
                try
                {
                    if (!this.CacheAccessible)
                        return result;

                    var dataCache = this.GetDataCache(retryCount > 1);
                    if (dataCache != null)
                    {
                        result = dataCache.Put(key, value, regionName);
                    }
                }

                catch (DataCacheException dataCacheException)
                {
                    shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                    retryCount++;
                    //if (shouldRetry == false)
                    //    throw;
                }
            } while (shouldRetry);

            return result;
        }

        protected override bool PutImpl(string key, object value, IEnumerable<string> cacheTags, string regionName)
        {
            if (!this.CacheAccessible)
                return false;

            bool result = false;

            if (this.PerformPut(key, value, cacheTags, regionName) != null)
                result = true;

            return result;
        }

        private DataCacheItemVersion PerformPut(string key, object value, IEnumerable<string> cacheTags, string regionName)
        {
            DataCacheItemVersion result = null;
            var retryCount = 1;
            var shouldRetry = false;
            do
            {
                try
                {
                    if (!this.CacheAccessible)
                        return result;

                    var dataCache = this.GetDataCache(retryCount > 1);
                    if (dataCache != null)
                    {
                        var dataCacheTags = cacheTags.Select(cacheTag => new DataCacheTag(cacheTag));

                        result = dataCache.Put(key, value, dataCacheTags, regionName);
                    }
                }
                catch (DataCacheException dataCacheException)
                {
                    shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                    retryCount++;
                    //if (shouldRetry == false)
                    //    throw;
                }
            } while (shouldRetry);

            return result;
        }

        protected override bool PutImpl(string key, object value, IEnumerable<string> cacheTags)
        {
            if (!this.CacheAccessible)
                return false;

            bool result = false;
            if (this.PerformPut(key, value, cacheTags) != null)
                result = true;
            return result;
        }

        private DataCacheItemVersion PerformPut(string key, object value, IEnumerable<string> cacheTags)
        {
            DataCacheItemVersion result = null;
            var retryCount = 1;
            var shouldRetry = false;

            do
            {
                try
                {
                    if (!this.CacheAccessible)
                        return result;

                    var dataCache = this.GetDataCache(retryCount > 1);
                    if (dataCache != null)
                    {
                        var dataCacheTags = cacheTags.Select(cacheTag => new DataCacheTag(cacheTag));

                        result = dataCache.Put(key, value, dataCacheTags);
                    }
                }
                catch (DataCacheException dataCacheException)
                {
                    shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                    retryCount++;
                    //if (shouldRetry == false)
                    //    throw;
                }
            } while (shouldRetry);

            return result;
        }

        protected override bool PutAndUnlockImpl(string key, object value, Guid lockId)
        {
            if (!this.CacheAccessible)
                return false;

            bool result = false;
            var dataCache = this.GetDataCache();

            CacheLockEntry cacheLockEntry;
            if (dataCache != null)
            {
                try
                {
                    CacheLockTableLocker.EnterReadLock();
                    cacheLockEntry = (from lockHandleEntry in this._cacheLockTable
                                      where lockHandleEntry.LockId.Equals(lockId)
                                      select lockHandleEntry).FirstOrDefault();

                }
                finally
                {
                    CacheLockTableLocker.ExitReadLock();
                }

                if (cacheLockEntry != null)
                {
                    dataCache.PutAndUnlock(key, value, cacheLockEntry.LockHandle);

                    try
                    {
                        CacheLockTableLocker.EnterWriteLock();
                        this._cacheLockTable.Remove(cacheLockEntry);
                        result = true;
                    }
                    finally
                    {
                        CacheLockTableLocker.ExitWriteLock();
                    }


                }
                else
                {
                    throw new ArgumentException("lockId");
                }
            }

            return result;
        }

        protected override bool PutAndUnlockImpl(string key, object value, Guid lockId, IEnumerable<string> cacheTags)
        {
            if (!this.CacheAccessible)
                return false;

            bool result = false;
            var dataCache = this.GetDataCache();
            CacheLockEntry cacheLockEntry;
            if (dataCache != null)
            {
                try
                {
                    CacheLockTableLocker.EnterReadLock();
                    cacheLockEntry = (from lockHandleEntry in this._cacheLockTable
                                      where lockHandleEntry.LockId.Equals(lockId)
                                      select lockHandleEntry).FirstOrDefault();

                }
                finally
                {
                    CacheLockTableLocker.ExitReadLock();
                }

                if (cacheLockEntry != null)
                {
                    var dataCacheTags = cacheTags.Select(cacheTag => new DataCacheTag(cacheTag));

                    dataCache.PutAndUnlock(key, value, cacheLockEntry.LockHandle, dataCacheTags);

                    try
                    {
                        CacheLockTableLocker.EnterWriteLock();
                        this._cacheLockTable.Remove(cacheLockEntry);
                        result = true;
                    }
                    finally
                    {
                        CacheLockTableLocker.ExitWriteLock();
                    }
                }
                else
                {
                    throw new ArgumentException("lockId");
                }
            }

            return result;
        }

        protected override bool PutAndUnlockImpl(string key, object value, Guid lockId, IEnumerable<string> cacheTags, string regionName)
        {
            if (!this.CacheAccessible)
                return false;

            bool result = false;
            var dataCache = this.GetDataCache();
            CacheLockEntry cacheLockEntry;
            if (dataCache != null)
            {
                try
                {
                    CacheLockTableLocker.EnterReadLock();
                    cacheLockEntry = (from lockHandleEntry in this._cacheLockTable
                                      where lockHandleEntry.LockId.Equals(lockId)
                                      select lockHandleEntry).FirstOrDefault();

                }
                finally
                {
                    CacheLockTableLocker.ExitReadLock();
                }

                if (cacheLockEntry != null)
                {

                    var dataCacheTags = cacheTags.Select(cacheTag => new DataCacheTag(cacheTag));

                    dataCache.PutAndUnlock(key, value, cacheLockEntry.LockHandle, dataCacheTags, regionName);
                    try
                    {
                        CacheLockTableLocker.EnterWriteLock();
                        this._cacheLockTable.Remove(cacheLockEntry);
                        result = true;
                    }
                    finally
                    {
                        CacheLockTableLocker.ExitWriteLock();
                    }
                }
                else
                {
                    throw new ArgumentException("lockId");
                }
            }

            return result;
        }

        protected override bool PutAndUnlockImpl(string key, object value, Guid lockId, string regionName)
        {
            if (!this.CacheAccessible)
                return false;

            bool result = false;
            var dataCache = this.GetDataCache();
            CacheLockEntry cacheLockEntry;
            if (dataCache != null)
            {
                try
                {
                    CacheLockTableLocker.EnterReadLock();
                    cacheLockEntry = (from lockHandleEntry in this._cacheLockTable
                                      where lockHandleEntry.LockId.Equals(lockId)
                                      select lockHandleEntry).FirstOrDefault();

                }
                finally
                {
                    CacheLockTableLocker.ExitReadLock();
                }

                dataCache.PutAndUnlock(key, value, cacheLockEntry.LockHandle, regionName);
                try
                {
                    CacheLockTableLocker.EnterWriteLock();
                    this._cacheLockTable.Remove(cacheLockEntry);
                    result = true;
                }
                finally
                {
                    CacheLockTableLocker.ExitWriteLock();
                }
            }

            return result;
        }

        protected override bool PutAndUnlockImpl(string key, object value, Guid lockId, TimeSpan timeout)
        {
            if (!this.CacheAccessible)
                return false;

            bool result = false;
            var dataCache = this.GetDataCache();
            CacheLockEntry cacheLockEntry;
            if (dataCache != null)
            {
                try
                {
                    CacheLockTableLocker.EnterReadLock();
                    cacheLockEntry = (from lockHandleEntry in this._cacheLockTable
                                      where lockHandleEntry.LockId.Equals(lockId)
                                      select lockHandleEntry).FirstOrDefault();

                }
                finally
                {
                    CacheLockTableLocker.ExitReadLock();
                }

                dataCache.PutAndUnlock(key, value, cacheLockEntry.LockHandle, timeout);
                try
                {
                    CacheLockTableLocker.EnterWriteLock();
                    this._cacheLockTable.Remove(cacheLockEntry);
                    result = true;
                }
                finally
                {
                    CacheLockTableLocker.ExitWriteLock();
                }
            }

            return result;
        }

        protected override bool PutAndUnlockImpl(string key, object value, Guid lockId, TimeSpan timeout, IEnumerable<string> cacheTags)
        {
            if (!this.CacheAccessible)
                return false;

            bool result = false;
            var dataCache = this.GetDataCache();
            CacheLockEntry cacheLockEntry;
            if (dataCache != null)
            {
                try
                {
                    CacheLockTableLocker.EnterReadLock();
                    cacheLockEntry = (from lockHandleEntry in this._cacheLockTable
                                      where lockHandleEntry.LockId.Equals(lockId)
                                      select lockHandleEntry).FirstOrDefault();

                }
                finally
                {
                    CacheLockTableLocker.ExitReadLock();
                }

                var dataCacheTags = cacheTags.Select(cacheTag => new DataCacheTag(cacheTag));

                dataCache.PutAndUnlock(key, value, cacheLockEntry.LockHandle, timeout, dataCacheTags);
                try
                {
                    CacheLockTableLocker.EnterWriteLock();
                    this._cacheLockTable.Remove(cacheLockEntry);
                    result = true;
                }
                finally
                {
                    CacheLockTableLocker.ExitWriteLock();
                }
            }

            return result;
        }

        protected override bool PutAndUnlockImpl(string key, object value, Guid lockId, TimeSpan timeout, IEnumerable<string> cacheTags,
                                                 string regionName)
        {
            if (!this.CacheAccessible)
                return false;

            bool result = false;
            var dataCache = this.GetDataCache();
            CacheLockEntry cacheLockEntry;
            if (dataCache != null)
            {
                try
                {
                    CacheLockTableLocker.EnterReadLock();
                    cacheLockEntry = (from lockHandleEntry in this._cacheLockTable
                                      where lockHandleEntry.LockId.Equals(lockId)
                                      select lockHandleEntry).FirstOrDefault();

                }
                finally
                {
                    CacheLockTableLocker.ExitReadLock();
                }

                var dataCacheTags = cacheTags.Select(cacheTag => new DataCacheTag(cacheTag));

                dataCache.PutAndUnlock(key, value, cacheLockEntry.LockHandle, timeout, dataCacheTags, regionName);
                try
                {
                    CacheLockTableLocker.EnterWriteLock();
                    this._cacheLockTable.Remove(cacheLockEntry);
                    result = true;
                }
                finally
                {
                    CacheLockTableLocker.ExitWriteLock();
                }
            }
            return result;
        }

        protected override bool PutAndUnlockImpl(string key, object value, Guid lockId, TimeSpan timeout, string regionName)
        {
            if (!this.CacheAccessible)
                return false;

            bool result = false;
            var dataCache = this.GetDataCache();
            CacheLockEntry cacheLockEntry;
            if (dataCache != null)
            {
                try
                {
                    CacheLockTableLocker.EnterReadLock();
                    cacheLockEntry = (from lockHandleEntry in this._cacheLockTable
                                      where lockHandleEntry.LockId.Equals(lockId)
                                      select lockHandleEntry).FirstOrDefault();

                }
                finally
                {
                    CacheLockTableLocker.ExitReadLock();
                }

                dataCache.PutAndUnlock(key, value, cacheLockEntry.LockHandle, timeout, regionName);


                try
                {
                    CacheLockTableLocker.EnterWriteLock();
                    this._cacheLockTable.Remove(cacheLockEntry);
                    result = true;
                }
                finally
                {
                    CacheLockTableLocker.ExitWriteLock();
                }
            }

            return result;
        }

        public override bool Put(string key, object value, DependencyInfo dependencyInfo)
        {
            return PutImpl(key, value, string.Empty, dependencyInfo, TimeSpan.Zero);
        }

        public override bool Put(string key, object value, DependencyInfo dependencyInfo, TimeSpan timeout)
        {
            return this.PutImpl(key, value, string.Empty, dependencyInfo, timeout);
        }

        protected override bool PutImpl(string key, object value, string regionName, DependencyInfo dependencyInfo)
        {
            if (!this.CacheAccessible)
                return false;

            return PutImpl(key, value, regionName, dependencyInfo, TimeSpan.Zero);
            //return AddOrUpdate<Object>(key, value, regionName, dependencyInfo, TimeSpan.Zero, null, null, null, ThreadOption.UIThread, false);
        }

        protected override bool PutImpl(string key, object value, string regionName, DependencyInfo dependencyInfo, TimeSpan timeout)
        {
            if (!this.CacheAccessible)
                return false;

            bool success = this.AddOrUpdate<object>(key, value, regionName, dependencyInfo, timeout, null, null, null, ThreadOption.UIThread, false);

            if (success)
            {
                var dataCache = this.GetDataCache();

                dataCache.AddItemLevelCallback(key, DataCacheOperations.RemoveItem, CacheItemRemovedCallback);
                dataCache.AddFailureNotificationCallback(CacheNotificationFailureCallback);
            }

            return success;
        }

        protected override bool PutImpl<T>(string key, object value, string regionName, TimeSpan timeout, IEnumerable<string> cacheTags,
                                           DependencyInfo dependencyInfo, Action<T> cacheUpdationCallback, T callbackState,
                                           ThreadOption threadOption, bool keepSubscriberReferenceAlive)
        {
            if (!this.CacheAccessible)
                return false;

            return this.InsertOrUpdate(key, value, regionName, dependencyInfo, timeout, cacheTags, cacheUpdationCallback, callbackState, threadOption, keepSubscriberReferenceAlive);
        }

        private bool InsertOrUpdate<T>(string key, object value, string regionName, DependencyInfo dependencyInfo, TimeSpan timeout, IEnumerable<string> cacheTags, Action<T> cacheUpdationCallback, T callbackState, ThreadOption threadOption, bool keepSubscriberReferenceAlive)
        {
            var success = this.AddOrUpdate(key, value, regionName, dependencyInfo, timeout, cacheTags, cacheUpdationCallback, callbackState,
                                           threadOption, keepSubscriberReferenceAlive);

            if (success)
            {
                var dataCache = this.GetDataCache();

                dataCache.AddItemLevelCallback(key, DataCacheOperations.RemoveItem, this.CacheItemRemovedCallback);
                dataCache.AddFailureNotificationCallback(this.CacheNotificationFailureCallback);
            }
            return success;
        }

        protected override bool PutImpl<T>(string key, object value, string regionName, bool useDefaultTimeout, IEnumerable<string> cacheTags, DependencyInfo dependencyInfo, Action<T> cacheUpdationCallback, T callbackState, ThreadOption threadOption, bool keepSubscriberReferenceAlive)
        {
            if (!this.CacheAccessible)
                return false;

            if (useDefaultTimeout)
            {
                var defaultTimeout = TimeSpan.FromMilliseconds(this.DefaultExpirationTimeInMilliSeconds);
                return this.InsertOrUpdate(key, value, regionName, dependencyInfo, defaultTimeout,
                                           cacheTags, cacheUpdationCallback, callbackState, threadOption, keepSubscriberReferenceAlive);
            }

            return false;
        }
        #endregion

        #region G E T   O V E R L O A D S

        public override object Get(string key)
        {
            //this._sqlMonitor.WaitForNotificationProcessingToComplete(key, string.Empty, this.CacheName);

            return this.Get<Object>(key);
        }

        public override T Get<T>(string key)
        {
            var message = string.Format("Get Cache with key {0} invoked.", key);
            this.LogWriter.Write(message, this.LoggingCategory, 1, (int)LoggingEvent.CacheGet, TraceEventType.Information, key);

            //this._sqlMonitor.WaitForNotificationProcessingToComplete(key, string.Empty, this.CacheName);

            var result = default(T);

            var retryCount = 1;
            var shouldRetry = false;

            if (!this.CacheAccessible)
                return result;
            do
            {
                try
                {
                    //Get the dataCache instance of the AppFabric server 
                    var dataCache = this.GetDataCache(retryCount > 1);

                    if (dataCache != null)
                    {
                        result = (T)dataCache.Get(key);
                    }
                }
                catch (DataCacheException dataCacheException)
                {
                    //If cache server is not accessible, exception will be thrown. In this case,
                    //framework will retry connecting to server for fetching data
                    shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                    retryCount++;
                    if (shouldRetry == false)
                        result = default(T);
                }
            } while (shouldRetry);


            return result;
        }

        private bool HandleCacheException(int retryCount, DataCacheException dataCacheException)
        {
            //Anticipating that we will get exceptions most of the time due to some server issue, 
            //not handling specific cases and writing a generic handler.
            const int maxRetryPossible = 1;


            try
            {
                const string messageFormat = "Exception While connecting to AppFabric Cache. Clearing The Connection Pool and retrying for {0} time.  Exception Details{1}";
                var messageToLog = string.Format(messageFormat, retryCount, dataCacheException.Message);
                this.LogWriter.Write(messageToLog, this.LoggingCategory, 1, (int)LoggingEvent.Error, TraceEventType.Critical, "DataCache");
                //Start a seperate thread to monitor the connectivity of the Cache Server instance.
                if (retryCount == 1 && this.CacheAccessible)
                    ThreadPool.QueueUserWorkItem(this.TryConnectingToCacheServer, null);
            }
            finally
            {
                retryCount++;
            }

            return retryCount < maxRetryPossible;
            //switch (dataCacheException.ErrorCode)
            //{
            //    case DataCacheErrorCode.RetryLater:
            //        string messageToLog;
            //        switch (dataCacheException.SubStatus)
            //        {
            //            case DataCacheErrorSubStatus.CacheServerUnavailable:
            //            case DataCacheErrorSubStatus.None:
            //                messageToLog = string.Format(messageFormat, dataCacheException.Message);
            //                this.LogWriter.Write(messageToLog, "TomcatLogging", 1, (int)LoggingEvent.Error, TraceEventType.Error, "DataCache");
            //                break;
            //            default:
            //                messageFormat = "Fatal Exception While connecting to AppFabric Cache.  Exception Details{0}";
            //                messageToLog = string.Format(messageFormat, dataCacheException.Message);
            //                this.LogWriter.Write(messageToLog, "TomcatLogging", 1, (int)LoggingEvent.Error, TraceEventType.Critical, "DataCache");
            //                break;
            //        }
            //        break;
            //    default:
            //        messageFormat = "Fatal Exception While connecting to AppFabric Cache.  Exception Details{0}";
            //        messageToLog = string.Format(messageFormat, dataCacheException.Message);
            //        this.LogWriter.Write(messageToLog, "TomcatLogging", 1, (int)LoggingEvent.Error, TraceEventType.Critical, "DataCache");
            //        break;
            //}


        }

        public override IDictionary<string, object> Get(params string[] keys)
        {
            if (!this.CacheAccessible)
                return null;

            Dictionary<string, object> result = null;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                result = new Dictionary<string, object>();
                foreach (var key in keys)
                {
                    result.Add(key, this.Get(key));
                }
            }
            return result;
        }

        protected override object GetAndLockImpl(string key, TimeSpan timeout, out Guid lockId)
        {
            object result = null;
            lockId = Guid.Empty;

            if (!this.CacheAccessible)
                return null;

            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                DataCacheLockHandle lockHandle;
                result = dataCache.GetAndLock(key, timeout, out lockHandle);
                //TODO:Add notification for removal so that the lockTable can be cleared-up.
                var lockEntry = new CacheLockEntry(lockHandle);

                this.AddToCacheLockTable(lockEntry);

                lockId = lockEntry.LockId;
            }

            return result;
        }

        protected override object GetAndLockImpl(string key, TimeSpan timeout, out Guid lockId, bool forceLock)
        {
            object result = null;
            lockId = Guid.Empty;

            if (!this.CacheAccessible)
                return null;


            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                DataCacheLockHandle lockHandle;
                result = dataCache.GetAndLock(key, timeout, out lockHandle, forceLock);
                var lockEntry = new CacheLockEntry(lockHandle);

                this.AddToCacheLockTable(lockEntry);

                lockId = lockEntry.LockId;
            }

            return result;
        }

        private void AddToCacheLockTable(CacheLockEntry lockEntry)
        {
            try
            {
                CacheLockTableLocker.EnterWriteLock();
                this._cacheLockTable.Add(lockEntry);
            }
            finally
            {
                CacheLockTableLocker.ExitWriteLock();
            }
        }

        protected override object GetAndLockImpl(string key, TimeSpan timeout, out Guid lockId, string regionName)
        {
            object result = null;
            lockId = Guid.Empty;

            if (!this.CacheAccessible)
                return null;

            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                DataCacheLockHandle lockHandle;
                result = dataCache.GetAndLock(key, timeout, out lockHandle, regionName);
                var lockEntry = new CacheLockEntry(lockHandle);

                this.AddToCacheLockTable(lockEntry);
                lockId = lockEntry.LockId;
            }

            return result;
        }

        protected override object GetAndLockImpl(string key, TimeSpan timeout, out Guid lockId, string regionName, bool forceLock)
        {
            object result = null;
            lockId = Guid.Empty;

            if (!this.CacheAccessible)
                return null;

            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                DataCacheLockHandle lockHandle;
                result = dataCache.GetAndLock(key, timeout, out lockHandle, regionName, forceLock);
                var lockEntry = new CacheLockEntry(lockHandle);

                this.AddToCacheLockTable(lockEntry);
                lockId = lockEntry.LockId;
            }

            return result;
        }

        protected override object GetImpl(string key, string regionName)
        {
            var message = string.Format("Get Cache with key {0} and region {1} invoked.", key, regionName);
            this.LogWriter.Write(message, this.LoggingCategory, 1, (int)LoggingEvent.CacheGet, TraceEventType.Information, key);

            //this._sqlMonitor.WaitForNotificationProcessingToComplete(key, string.Empty, this.CacheName);
            if (!this.CacheAccessible)
                return null;

            object result = null;

            var retryCount = 1;
            var shouldRetry = false;

            do
            {
                try
                {
                    var dataCache = this.GetDataCache(retryCount > 1);

                    if (dataCache != null)
                    {
                        result = dataCache.Get(key, regionName);
                    }
                }

                catch (DataCacheException dataCacheException)
                {
                    shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                    retryCount++;
                    //if (shouldRetry == false)
                    //    throw;
                }
            } while (shouldRetry);


            return result;
        }

        protected override IEnumerable<KeyValuePair<string, object>> GetObjectsByAllTagsImpl(IEnumerable<string> cacheTags, string regionName)
        {
            if (!this.CacheAccessible)
                return null;

            IEnumerable<KeyValuePair<string, object>> result = null;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                var dataCacheTags = cacheTags.Select(cacheTag => new DataCacheTag(cacheTag));
                result = dataCache.GetObjectsByAllTags(dataCacheTags, regionName);
            }

            return result;
        }

        protected override IEnumerable<KeyValuePair<string, object>> GetObjectsByAnyTagImpl(IEnumerable<string> cacheTags, string regionName)
        {
            if (!this.CacheAccessible)
                return null;

            IEnumerable<KeyValuePair<string, object>> result = null;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                var dataCacheTags = cacheTags.Select(cacheTag => new DataCacheTag(cacheTag));
                result = dataCache.GetObjectsByAnyTag(dataCacheTags, regionName);
            }

            return result;
        }

        protected override IEnumerable<KeyValuePair<string, object>> GetObjectsByTagImpl(string cacheTag, string regionName)
        {
            if (!this.CacheAccessible)
                return null;

            IEnumerable<KeyValuePair<string, object>> result = null;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                var dataCacheTag = new DataCacheTag(cacheTag);
                result = dataCache.GetObjectsByTag(dataCacheTag, regionName);
            }

            return result;
        }

        protected override IEnumerable<KeyValuePair<string, object>> GetObjectsInRegionImpl(string regionName)
        {
            if (!this.CacheAccessible)
                return null;

            IEnumerable<KeyValuePair<string, object>> result = null;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                result = dataCache.GetObjectsInRegion(regionName);
            }

            return result;
        }

        protected override string GetSystemRegionNameImpl(string key)
        {
            if (!this.CacheAccessible)
                return null;

            string result = null;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                result = dataCache.GetSystemRegionName(key);
            }

            return result;
        }

        protected override IEnumerable<string> GetSystemRegionsImpl()
        {
            if (!this.CacheAccessible)
                return null;

            IEnumerable<string> result = null;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                result = dataCache.GetSystemRegions();
            }

            return result;
        }

        protected override bool IsCacheVersionSameImpl(object earlierVersion, string key, string regionName)
        {
            if (!this.CacheAccessible)
                return true;

            DataCacheItem dataCacheItem = null;
            var retryCount = 1;
            var shouldRetry = false;

            do
            {
                try
                {
                    var dataCache = this.GetDataCache();
                    if (dataCache != null)
                    {
                        if (!string.IsNullOrEmpty(regionName))
                        {
                            dataCacheItem = dataCache.GetCacheItem(key, regionName);

                        }
                        else
                            dataCacheItem = dataCache.GetCacheItem(key);
                    }
                }
                catch (DataCacheException dataCacheException)
                {
                    shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                    retryCount++;
                    //if (shouldRetry == false)
                    //    throw;
                }
            } while (shouldRetry);


            if (dataCacheItem != null)
            {
                var currentCacheItemVersion = dataCacheItem.Version;

                var earlierCacheItemVersion = GetDataCacheItemVersion(earlierVersion);

                if (currentCacheItemVersion.Equals(earlierCacheItemVersion))
                    return true;

            }

            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="firstDataCacheVersion">Serializable version of DataCacheItemVersion object</param>
        /// <param name="secondDataCacheVersion">Serializable version of DataCacheItemVersion object</param>
        /// <returns>true if both the versions are same.</returns>
        protected override bool IsCacheVersionSameImpl(object firstDataCacheVersion, object secondDataCacheVersion)
        {
            if (!this.CacheAccessible)
                return true;

            var areCacheVersionsSame = false;
            var firstCacheItemVersion = GetDataCacheItemVersion(firstDataCacheVersion);
            var secondCacheItemVersion = GetDataCacheItemVersion(secondDataCacheVersion);

            if (firstCacheItemVersion != null && secondCacheItemVersion != null)
            {
                if (firstCacheItemVersion.Equals(secondCacheItemVersion))
                    areCacheVersionsSame = true;
            }

            return areCacheVersionsSame;
        }

        #endregion

        #region R E M O V E   O V E R L O A D S

        public override bool Remove(string key)
        {
            var message = string.Format("Remove Cache with key {0} invoked.", key);
            this.LogWriter.Write(message, this.LoggingCategory, 1, (int)LoggingEvent.CacheRemove, TraceEventType.Information, key);

            if (!this.CacheAccessible)
                return false;

            var result = false;
            var retryCount = 1;
            var shouldRetry = false;

            do
            {
                try
                {
                    var dataCache = this.GetDataCache();
                    if (dataCache != null)
                    {
                        result = dataCache.Remove(key);
                    }
                }

                catch (DataCacheException dataCacheException)
                {
                    shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                    retryCount++;
                    //if (shouldRetry == false)
                    //    throw;
                }
            } while (shouldRetry);



            return result;
        }

        public override void RemoveAll()
        {
            throw new NotImplementedException();
        }

        protected override bool RemoveImpl(string key, Guid lockId)
        {
            if (!this.CacheAccessible)
                return false;

            bool result = false;
            var dataCache = this.GetDataCache();
            CacheLockEntry cacheLockEntry;
            if (dataCache != null)
            {
                try
                {
                    CacheLockTableLocker.EnterReadLock();
                    cacheLockEntry = (from lockHandleEntry in this._cacheLockTable
                                      where lockHandleEntry.LockId.Equals(lockId)
                                      select lockHandleEntry).FirstOrDefault();

                }
                finally
                {
                    CacheLockTableLocker.ExitReadLock();
                }

                if (cacheLockEntry != null)
                {
                    dataCache.Remove(key, cacheLockEntry.LockHandle);

                    try
                    {
                        CacheLockTableLocker.EnterWriteLock();
                        this._cacheLockTable.Remove(cacheLockEntry);
                        result = true;
                    }
                    finally
                    {
                        CacheLockTableLocker.ExitWriteLock();
                    }
                }
            }

            return result;
        }

        protected override bool RemoveImpl(string key, Guid lockId, string regionName)
        {
            if (!this.CacheAccessible)
                return false;

            bool result = false;
            var dataCache = this.GetDataCache();
            CacheLockEntry cacheLockEntry;
            if (dataCache != null)
            {

                try
                {
                    CacheLockTableLocker.EnterReadLock();
                    cacheLockEntry = (from lockHandleEntry in this._cacheLockTable
                                      where lockHandleEntry.LockId.Equals(lockId)
                                      select lockHandleEntry).FirstOrDefault();

                }
                finally
                {
                    CacheLockTableLocker.ExitReadLock();
                }

                if (cacheLockEntry != null)
                {
                    dataCache.Remove(key, cacheLockEntry.LockHandle, regionName);

                    try
                    {
                        CacheLockTableLocker.EnterWriteLock();
                        this._cacheLockTable.Remove(cacheLockEntry);
                        result = true;
                    }
                    finally
                    {
                        CacheLockTableLocker.ExitWriteLock();
                    }
                }
            }

            return result;
        }

        protected override bool RemoveImpl(string key, string regionName)
        {
            var message = string.Format("Remove Cache with key {0} in Region {1} invoked.", key, regionName);
            this.LogWriter.Write(message, this.LoggingCategory, 1, (int)LoggingEvent.CacheRemove, TraceEventType.Information, key);

            if (!this.CacheAccessible)
                return false;

            var result = false;
            var retryCount = 1;
            var shouldRetry = false;

            do
            {
                try
                {
                    var dataCache = this.GetDataCache();
                    if (dataCache != null)
                    {
                        result = dataCache.Remove(key, regionName);
                    }
                }
                catch (DataCacheException dataCacheException)
                {
                    shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                    retryCount++;
                    //if (shouldRetry == false)
                    //    throw;
                }
            } while (shouldRetry);


            return result;
        }

        protected override bool RemoveRegionImpl(string regionName)
        {
            var message = string.Format("Remove Region For RegionName: {0} invoked.", regionName);
            this.LogWriter.Write(message, this.LoggingCategory, 1, (int)LoggingEvent.CacheRemove, TraceEventType.Information, "Region: " + regionName);

            if (!this.CacheAccessible)
                return false;

            var result = false;
            var retryCount = 1;
            var shouldRetry = false;

            do
            {
                try
                {
                    var dataCache = this.GetDataCache();
                    if (dataCache != null)
                    {
                        result = dataCache.RemoveRegion(regionName);
                    }
                }
                catch (DataCacheException dataCacheException)
                {
                    shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                    retryCount++;
                    //if (shouldRetry == false)
                    //    throw;
                }
            } while (shouldRetry);


            return result;

        }

        #endregion

        #region M I S C E L L A N E O U S   M E T H O D S

        protected override bool CreateRegionImpl(string regionName)
        {
            if (!this.CacheAccessible)
                return false;

            bool result = false;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                result = dataCache.CreateRegion(regionName);
            }

            return result;
        }

        protected override bool ClearRegionImpl(string regionName)
        {
            if (!this.CacheAccessible)
                return false;

            bool result = false;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                dataCache.ClearRegion(regionName);
                result = true;
            }

            return result;
        }

        protected override void ResetObjectTimeoutImpl(string key, TimeSpan newTimeout)
        {
            if (!this.CacheAccessible)
                return;

            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                dataCache.ResetObjectTimeout(key, newTimeout);
            }
        }

        protected override void ResetObjectTimeoutImpl(string key, TimeSpan newTimeout, string regionName)
        {
            if (!this.CacheAccessible)
                return;

            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                dataCache.ResetObjectTimeout(key, newTimeout, regionName);
            }
        }

        protected override void SetNamedCacheImpl(string cacheName)
        {
            try
            {
                NamedCacheThreadTableLocker.EnterWriteLock();
                var currentThreadId = Thread.CurrentThread.ManagedThreadId;

                if (!string.IsNullOrEmpty(cacheName))
                {
                    if (!this._namedCacheThreadTable.ContainsKey(currentThreadId))
                        this._namedCacheThreadTable.Add(currentThreadId, cacheName);
                }
                else
                {
                    if (this._namedCacheThreadTable.ContainsKey(currentThreadId))
                        this._namedCacheThreadTable.Remove(currentThreadId);
                }
            }
            finally
            {
                NamedCacheThreadTableLocker.ExitWriteLock();
            }
        }

        public DataCacheNotificationDescriptor AddItemLevelCallback(string key, DataCacheOperations filter,
                                                                    DataCacheNotificationCallback clientCallback, string regionName)
        {
            if (!this.CacheAccessible)
                return null;

            DataCacheNotificationDescriptor notificationDescriptor = null;
            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                if (string.IsNullOrEmpty(regionName))
                    notificationDescriptor = dataCache.AddItemLevelCallback(key, filter, clientCallback);
                else
                    notificationDescriptor = dataCache.AddItemLevelCallback(key, filter, clientCallback, regionName);
            }

            return notificationDescriptor;
        }


        public void RemoveCallback(DataCacheNotificationDescriptor notificationDescriptor)
        {
            if (!this.CacheAccessible)
                return;

            var dataCache = this.GetDataCache();
            if (dataCache != null)
            {
                dataCache.RemoveCallback(notificationDescriptor);
            }
        }



        #endregion

        #region P R I V A T E   M E T H O D S

        private bool AddOrUpdate<T>(string key, object value, string regionName, DependencyInfo dependencyInfo, TimeSpan timeout, IEnumerable<string> cacheTags, Action<T> cacheUpdationCallback, T callbackState, ThreadOption threadOption, bool keepSubscriberReferenceAlive)
        {
            bool result = false;

            if (dependencyInfo != null)
            {
                switch (dependencyInfo.CacheDependencyType)
                {
                    case CacheDependencyTypes.File:
                        throw new NotSupportedException();
                    case CacheDependencyTypes.FileNCacheKey:
                        throw new NotSupportedException();
                    //case DependencyInfo.CacheDependencyTypes.SQLCacheDependency:
                    //    throw new NotSupportedException();
                    case CacheDependencyTypes.SQLDependency:
                        //RD starts 6th-Dec-2010
                        //result = this.ProcessPutWithSqlDependency<T>(dependencyInfo, timeout, key, value, regionName, cacheTags, cacheUpdationCallback, callbackState, threadOption, keepSubscriberReferenceAlive);
                        result = this.ProcessPutWithSqlDependency<T>(dependencyInfo, timeout, key, value, regionName, cacheTags, cacheUpdationCallback, callbackState, threadOption, keepSubscriberReferenceAlive);
                        //RD ends 6th-Dec-2010
                        break;
                    default:
                        throw new NotSupportedException(dependencyInfo.CacheDependencyType.ToString() + " is not supported");
                }
            }

            return result;
        }

        //RD starts 6th-Dec-2010

        private bool ProcessPutWithSqlDependency<T>(DependencyInfo dependencyInfo, TimeSpan timeout, string key, object value, string regionName, IEnumerable<string> cacheTags, Action<T> cacheUpdationCallback, T callbackState, ThreadOption threadOption, bool keepSubscriberReferenceAlive)
        {
            var result = false;

            if (!string.IsNullOrEmpty(dependencyInfo.DBConnectionString)
                && (
                        (dependencyInfo.IsSQLSelectQueryBased && dependencyInfo.SelectQueries.Count > 0)
                    || (dependencyInfo.IsSQLTableBased && dependencyInfo.SQLTables.Count > 0)
                    || (dependencyInfo.ISDbCommandBased && dependencyInfo.DbCommands.Count > 0)
                   )
               )
            {

                var status = this._sqlMonitor.StartMonitoring(dependencyInfo.DBConnectionString);

                if (status != CacheDependencyStatus.MonitoringStarted
                    && status != CacheDependencyStatus.AlreadyMonitoring)
                {
                    var cacheProviderErrorEventArgs = new CacheProviderErrorEventArgs()
                    {
                        Sender = this,
                        CacheKey = key,
                        ErrorMessage = "SQL Monitoring could not be started"
                    };
                    this.EventAggregatorInstance.GetEvent<CacheProviderErrorEvent>().Publish(cacheProviderErrorEventArgs);

                    return result;
                }

                DataCacheItemVersion dataCacheItemVersion = null;

                if (!string.IsNullOrEmpty(regionName))
                {
                    if (timeout == TimeSpan.Zero)
                    {
                        if (cacheTags != null && cacheTags.Count() > 0)
                            dataCacheItemVersion = this.PerformPut(key, value, cacheTags, regionName);
                        else
                            dataCacheItemVersion = this.PerformPut(key, value, regionName);
                    }
                    else
                    {
                        if (cacheTags != null && cacheTags.Count() > 0)
                            dataCacheItemVersion = this.PerformPut(key, value, timeout, cacheTags, regionName);
                        else
                            dataCacheItemVersion = this.PerformPut(key, value, timeout, regionName);
                    }
                }
                else
                {
                    if (timeout == TimeSpan.Zero)
                    {
                        if (cacheTags != null && cacheTags.Count() > 0)
                            dataCacheItemVersion = this.PerformPut(key, value, cacheTags);
                        else
                            dataCacheItemVersion = this.PerformPut(key, value);
                    }
                    else
                    {
                        if (cacheTags != null && cacheTags.Count() > 0)
                            dataCacheItemVersion = this.PerformPut(key, value, timeout, cacheTags);
                        else
                            dataCacheItemVersion = this.PerformPut(key, value, timeout);
                    }
                }

                if (dataCacheItemVersion != null) result = true;

                if (result)
                {
                    try
                    {
                        var serializableCacheItemVersion = GetSerializableCacheItemVersion(dataCacheItemVersion);

                        this._sqlMonitor.AddSqlDependencies(key, this.CacheName, regionName, dependencyInfo,
                                                          true, cacheUpdationCallback, callbackState, threadOption,
                                                          keepSubscriberReferenceAlive,
                                                          TimeSpan.Zero, serializableCacheItemVersion);
                    }
                    catch (Exception)//In case of exception, remove the cache entry.
                    {
                        if (IsRegionSupported && !string.IsNullOrEmpty(regionName))
                            this.Remove(key, regionName);
                        else
                            this.Remove(key);
                        throw;
                    }
                }
            }
            else
            {
                const string errorMessage = @"Properties [DBConnectionString],([SelectQueries] and  [IsSQLSelectQuery=true]) AND\OR (SQLTables and IsSQLTable) AND\OR (DBCommands and ISDbCommandBased) must be set for CacheDependencyType [SQLDependency]";
                var cacheProviderErrorEventArgs = new CacheProviderErrorEventArgs()
                {
                    Sender = this,
                    CacheKey = key,
                    ErrorMessage = errorMessage,
                    State = dependencyInfo
                };

                this.EventAggregatorInstance.GetEvent<CacheProviderErrorEvent>().Publish(cacheProviderErrorEventArgs);
                //throw new InvalidOperationException(@"Properties [DBConnectionString],([SelectQueries] and  [IsSQLSelectQuery=true]) AND\OR (SQLTables and IsSQLTable) must be set for CacheDependencyType [SQLDependency]");
            }



            return result;
        }

        //RD Ends 6th-Dec-2010

        /// <summary>
        /// The class <see cref="DataCacheItemVersion" /> is not serializable, 
        /// however the internal class <see cref="InternalCacheItemVersion"/> is actually serializable. />
        /// and is used by the former one. Using Reflection, returning the same so that it can be cached.
        /// </summary>
        /// <param name="dataCacheItemVersion">An instance of <see cref="DataCacheItemVersion" /></param>
        /// <returns>Returns an instance of <see cref="InternalCacheItemVersion"/> as object because the type is internal</returns>
        private static object GetSerializableCacheItemVersion(DataCacheItemVersion dataCacheItemVersion)
        {
            if (dataCacheItemVersion == null) return null;

            Type objectType = dataCacheItemVersion.GetType();
            var propertyInfo = objectType.GetProperty("InternalVersion", BindingFlags.NonPublic | BindingFlags.Instance);
            if (propertyInfo == null)
                Debug.Assert(propertyInfo != null, "Fatal Exception! Internal property for DataCacheItemVersion has changed");

            var internalCacheItemVersion = propertyInfo.GetValue(dataCacheItemVersion, null);
            return internalCacheItemVersion;

        }

        private DataCache GetDataCache()
        {
            var retryCount = 1;
            DataCache dataCache = null;
            var shouldRetry = false;

            do
            {
                try
                {
                    dataCache = this.GetDataCache(false);
                }
                catch (DataCacheException dataCacheException)
                {
                    shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                    retryCount++;
                    //Retry wont throw the exception. Instead return a null object to continue with
                    //Cache disabled status
                    //if (shouldRetry == false)
                    //    throw;
                }
            } while (shouldRetry);

            return dataCache;
        }

        private void TryConnectingToCacheServer(object state)
        {
            /*
             1. Set the CacheAccessible flag to false.
             2. Try connecting to cache till it is connected and then, perform an operation on Cache Server.
             3. If it is successful, clear the namedCache to avoid any stale data.
             4. Reset the CacheAccessible flag to true.
             */
            var shouldRetry = true;
            this.CacheAccessible = false;
            var noOfRetry = 0;

            try
            {
                do
                {
                    try
                    {
                        var message = "Cache server is not accessible. Monitoring of cache server for availability.";
                        this.LogWriter.Write(message, this.LoggingCategory, 1, (int)LoggingEvent.DataCacheError,
                              TraceEventType.Critical, "TryConnectingToCacheServer");

                        var dataCache = this.GetDataCache(true);
                        if (dataCache != null)
                        {
                            //Try to get a dummy object from data cache server to check whether
                            //cache is accessible.
                            var result = dataCache.Get("DummyKey");
                            var updatedCacheClientInfo = this.RefreshCacheClientsAndAddThis();
                            _sqlMonitor.RemoveAllDependencyTrackers();

                            try
                            {
                                CacheLockTableLocker.EnterWriteLock();
                                _cacheLockTable.Clear();
                            }
                            finally
                            {
                                CacheLockTableLocker.ExitWriteLock();
                            }

                            //Clear all the regions in the namedCache as data present in the region
                            //may not be uptodate.
                            var regions = dataCache.GetSystemRegions();
                            foreach (var region in regions)
                                dataCache.ClearRegion(region);

                            //Reset the CacheAccessible flag. So that Put/Get can continue using dataCache.
                            this.CacheAccessible = true;
                            shouldRetry = false;
                            this.UpdateDependencyTrackerCollectionCore(updatedCacheClientInfo);
                            message = "Cache server is up and running.";
                            this.LogWriter.Write(message, this.LoggingCategory, 1, (int)LoggingEvent.DataCacheError,
                                  TraceEventType.Critical, "TryConnectingToCacheServer");
                        }
                    }
                    catch (DataCacheException dataCacheException)
                    {
                        var message = dataCacheException.Message;
                        var key = string.Format("Status:{0} <-> Substatus:{1}", dataCacheException.ErrorCode, dataCacheException.SubStatus);
                        this.LogWriter.Write(message, this.LoggingCategory, 1, (int)LoggingEvent.DataCacheError,
                                             TraceEventType.Critical, key);
                        var delayTime = 1000;
                        Thread.Sleep((int)delayTime);
                        noOfRetry++;
                    }
                } while (shouldRetry);
            }
            finally
            {
                this.CacheAccessible = true;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="clearPoolFirst"></param>
        /// <returns></returns>
        private DataCache GetDataCache(bool clearPoolFirst)
        {
            DataCache dataCache = null;

            if (clearPoolFirst)
                this._dataCacheFactoryPool.ClearPool();

            var dataCacheFactory = this._dataCacheFactoryPool.GetDataCacheFactory();
            if (dataCacheFactory == null)
            {
                if (this.CacheAccessible)
                    ThreadPool.QueueUserWorkItem(this.TryConnectingToCacheServer);
                return dataCache;
            }


            if (string.IsNullOrEmpty(this.CacheName))
                dataCache = dataCacheFactory.GetDefaultCache();
            else
            {
                dataCache = dataCacheFactory.GetCache(this.CacheName);
            }

            return dataCache;
        }

        public void CacheNotificationFailureCallback(string cacheName, DataCacheNotificationDescriptor notificationDescriptor)
        {

            var errorDetails = new CacheProviderErrorEventArgs
            {
                CacheKey = @"N/A",
                ErrorMessage = string.Format("Notification Failure for Named Cache:{0}", cacheName)
            };

            this.EventAggregatorInstance.GetEvent<CacheProviderErrorEvent>().Publish(errorDetails);

        }

        private static DataCacheItemVersion GetDataCacheItemVersion(object earlierVersion)
        {
            if (earlierVersion != null)
            {
                var dataCacheItemVersion = Activator.CreateInstance(
                                                                    typeof(DataCacheItemVersion),
                                                                    BindingFlags.Instance | BindingFlags.NonPublic,
                                                                    null,
                                                                    new[] { earlierVersion },
                                                                    null,
                                                                    null
                                                                    );

                if (dataCacheItemVersion != null)
                {
                    if (dataCacheItemVersion is DataCacheItemVersion)
                        return dataCacheItemVersion as DataCacheItemVersion;

                    Debug.Assert(dataCacheItemVersion as DataCacheItemVersion != null, "Conversion to DataCacheItemVersion failed");
                }
            }

            return null;
        }

        public void CacheItemRemovedCallback(string cacheName,
                                             string region,
                                             string key,
                                             DataCacheItemVersion itemVersion,
                                             DataCacheOperations operationId,
                                             DataCacheNotificationDescriptor notificationDescriptor)
        {
            if (operationId == DataCacheOperations.RemoveItem)
            {
                var serializableCacheItemVersion = GetSerializableCacheItemVersion(itemVersion);
                this._sqlMonitor.RemoveSqlDependency(key, cacheName, region, serializableCacheItemVersion);

                var message = string.Format("Cache with Key {0} in Region {1} under Named Cache {2} removed", key, region, cacheName);
                if (this.LogWriter != null)
                    this.LogWriter.Write(message, this.LoggingCategory, 1, (int)LoggingEvent.CacheGet, TraceEventType.Information, key);
            }
        }

        #endregion

        protected override void BootstrapContainer()
        {
            var assemblyResourceName = "assembly://" + Assembly.GetExecutingAssembly().GetName().Name + "/AppFabricContainerConfig.xml";
            this.DIContainer.InitializeWindsorContainer(assemblyResourceName);
        }

        /// <summary>
        ///      Function used to update the Tracker Collection entry in cache
        ///      whenever a new client try to register to cache provider.
        /// </summary>
        /// <param name="updatedCacheClientInfo"></param>
        private void UpdateDependencyTrackerCollectionCore(CacheClientsInfo updatedCacheClientInfo)
        {
            var appFabricCacheClients = updatedCacheClientInfo.AppFabricCacheClients;
            var dataCacheItem = updatedCacheClientInfo.DataCacheItem;

            var retryCount = 1;
            var shouldRetry = false;

            do
            {
                try
                {
                    var dataCache = this.GetDataCache();
                    if (dataCache != null)
                    {
                        DataCacheItemVersion newCacheItemVersion = null;

                        if (dataCacheItem != null)
                            newCacheItemVersion = dataCache.Put(this._dependencyTrackerCollectionKey,
                                                                appFabricCacheClients,
                                                                dataCacheItem.Version);
                        else
                        {
                            try
                            {
                                newCacheItemVersion = dataCache.Add(this._dependencyTrackerCollectionKey, appFabricCacheClients);
                            }
                            catch (DataCacheException dataCacheException)
                            {
                                //In between if the key is added.  We will handle it in the code below.
                                if (dataCacheException.ErrorCode != DataCacheErrorCode.KeyAlreadyExists)
                                    throw;
                            }
                        }

                        //Here, if the control enters the while loop, it means
                        //that there is already a cache entry with this key but
                        //it got updated in between.
                        while (newCacheItemVersion == null)
                        {
                            updatedCacheClientInfo = this.RefreshCacheClientsAndAddThis();

                            Debug.Assert(updatedCacheClientInfo.AppFabricCacheClients != null, "appFabricCacheClients must not be null here");

                            //appFabricCacheClients.Add(thisClientInfo);
                            //No need to check for Add() case here.
                            newCacheItemVersion = dataCache.Put(this._dependencyTrackerCollectionKey,
                                                                updatedCacheClientInfo.AppFabricCacheClients,
                                                                updatedCacheClientInfo.DataCacheItem.Version);
                        }
                    }
                }
                catch (DataCacheException dataCacheException)
                {
                    shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                    retryCount++;
                    //if (shouldRetry == false)
                    //    throw;
                }
            } while (shouldRetry);
        }

        /// <summary>
        ///     Overriden method to update the Dependency tracker collection
        ///     with number of clients connected to cache server. 
        /// </summary>
        protected override void UpdateDependencyTrackerCollection()
        {
            var updatedCacheClientInfo = this.RefreshCacheClientsAndAddThis();
            this.UpdateDependencyTrackerCollectionCore(updatedCacheClientInfo);
        }

        /// <summary>
        ///     Function used to refresh the list of 
        ///     applications currently tracking items in cache server
        /// </summary>
        /// <returns></returns>
        private CacheClientsInfo RefreshCacheClientsAndAddThis()
        {
            DataCacheItem dataCacheItem = null;
            var cacheClientsInfo = new CacheClientsInfo();
            var isClientInfoExists = false;
            dataCacheItem = this.GetDataCacheItem(this._dependencyTrackerCollectionKey, string.Empty);

            var applicationName = ConfigurationManager.AppSettings["ApplicationName"];
            var macAddress = this.GetMACAddress();
            //If DependencyTrackerCollection exists in cache, then we need to identify whether
            //current CacheClientNodeIdentifier already exists. If exists, then just update
            //the IsClientAlive property to true.
            if (dataCacheItem != null)
            {
                cacheClientsInfo.AppFabricCacheClients = dataCacheItem.Value as List<AppFabricCacheClientInfo>;
                cacheClientsInfo.DataCacheItem = dataCacheItem;
                Debug.Assert(cacheClientsInfo.AppFabricCacheClients != null, "appFabricCacheClients must not be null here");
                //LINQ query to check whether current CacheClientNodeIdentifier exists in the tracker collection
                var thisClientAlreadyRegistered = (from clientInfo in cacheClientsInfo.AppFabricCacheClients
                                                   where clientInfo.ClientIdentifier.Equals(this.CacheClientNodeIdentifier)
                                                   select clientInfo).SingleOrDefault();

                if (thisClientAlreadyRegistered != null)
                {
                    thisClientAlreadyRegistered.IsClientAlive = true;
                    isClientInfoExists = true;
                }
            }

            //If current CacheClientNodeIdentifier doesn't exists in the tracker collection,
            //then this collection should be updated with new AppFabricCacheClientInfo.
            if (!isClientInfoExists)
            {
                var thisClientInfo = new AppFabricCacheClientInfo()
                {
                    ApplicationName = applicationName,
                    ClientIdentifier = this.CacheClientNodeIdentifier,
                    MACAddress = macAddress,
                    IsClientAlive = true,
                    //TODO: fill this IPAddress 
                };
                cacheClientsInfo.AppFabricCacheClients.Add(thisClientInfo);
            }
            //if (dataCacheItem == null)
            //{
            //    var thisClientInfo = new AppFabricCacheClientInfo()
            //                             {
            //                                 ApplicationName = applicationName,
            //                                 ClientIdentifier = this.CacheClientNodeIdentifier,
            //                                 MACAddress = macAddress,
            //                                 IsClientAlive = true,
            //                                 //TODO: fill this IPAddress 
            //                             };
            //    cacheClientsInfo.AppFabricCacheClients.Add(thisClientInfo);
            //}
            //else
            //{
            //    cacheClientsInfo.AppFabricCacheClients = dataCacheItem.Value as List<AppFabricCacheClientInfo>;
            //    cacheClientsInfo.DataCacheItem = dataCacheItem;
            //    Debug.Assert(cacheClientsInfo.AppFabricCacheClients != null, "appFabricCacheClients must not be null here");
            //    var thisClientAlreadyRegistered = (from clientInfo in cacheClientsInfo.AppFabricCacheClients
            //                                       where clientInfo.ClientIdentifier.Equals(this.CacheClientNodeIdentifier)
            //                                       select clientInfo).SingleOrDefault();

            //    if (thisClientAlreadyRegistered != null)
            //    {
            //        thisClientAlreadyRegistered.IsClientAlive = true;
            //    }
            //    else
            //    {
            //        var thisClientInfo = new AppFabricCacheClientInfo()
            //        {
            //            ApplicationName = applicationName,
            //            ClientIdentifier = this.CacheClientNodeIdentifier,
            //            MACAddress = macAddress,
            //            IsClientAlive = true,
            //            //TODO: fill this IPAddress 
            //        };
            //        cacheClientsInfo.AppFabricCacheClients.Add(thisClientInfo);
            //    }
            //}
            return cacheClientsInfo;
        }

        private DataCacheItem GetDataCacheItem(string key, string region)
        {
            var retryCount = 1;
            var shouldRetry = false;
            DataCacheItem dataCacheItem = null;

            do
            {
                try
                {
                    var dataCache = this.GetDataCache();
                    if (dataCache != null)
                    {
                        if (IsRegionSupported && !string.IsNullOrEmpty(region))
                            dataCacheItem = dataCache.GetCacheItem(key, region);
                        else
                            dataCacheItem = dataCache.GetCacheItem(key);
                    }

                }
                catch (DataCacheException dataCacheException)
                {
                    shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                    retryCount++;
                    //if (shouldRetry == false)
                    //    throw;
                }
            } while (shouldRetry);

            return dataCacheItem;
        }

        /// <summary>
        ///     Method used by external application to notify cache provider instance 
        ///     to unregister this client information. Once client has been unregistered,  
        ///     existing notification for these clients will be handled by cache dependency
        ///     windows service to invalidate the cache item.
        /// </summary>
        /// <param name="clientClosing"></param>
        protected override void NotifyCacheServerImpl(bool clientClosing)
        {
            DataCacheItem dataCacheItem = null;
            var shouldRetry = false;

            dataCacheItem = this.GetDataCacheItem(this._dependencyTrackerCollectionKey, string.Empty);

            if (dataCacheItem != null)
            {
                var appFabricCacheClients = dataCacheItem.Value as List<AppFabricCacheClientInfo>;

                var thisCacheClientInfo = (from cacheClient in appFabricCacheClients
                                           where cacheClient.ClientIdentifier.Equals(this.CacheClientNodeIdentifier)
                                           select cacheClient).SingleOrDefault();

                if (thisCacheClientInfo != null)
                {
                    thisCacheClientInfo.IsClientAlive = false;


                    var retryCount = 1;

                    do
                    {
                        try
                        {
                            var dataCache = this.GetDataCache();
                            if (dataCache != null)
                            {
                                DataCacheItemVersion newCacheItemVersion = null;
                                if (dataCacheItem != null)
                                    newCacheItemVersion = dataCache.Put(this._dependencyTrackerCollectionKey, appFabricCacheClients, dataCacheItem.Version);


                                //Here, if the control enters the while loop, it means
                                //that there is already a cache entry with this key but
                                //it got updated in between.
                                while (newCacheItemVersion == null)
                                {
                                    dataCacheItem = dataCache.GetCacheItem(this._dependencyTrackerCollectionKey);

                                    appFabricCacheClients = dataCacheItem.Value as List<AppFabricCacheClientInfo>;

                                    thisCacheClientInfo = (from cacheClient in appFabricCacheClients
                                                           where cacheClient.ClientIdentifier.Equals(this.CacheClientNodeIdentifier)
                                                           select cacheClient).Single();

                                    if (thisCacheClientInfo != null)
                                    {
                                        thisCacheClientInfo.IsClientAlive = false;
                                    }

                                    Debug.Assert(appFabricCacheClients != null, "appFabricCacheClients must not be null here");

                                    newCacheItemVersion = dataCache.Put(this._dependencyTrackerCollectionKey, appFabricCacheClients, dataCacheItem.Version);
                                }

                                var message = string.Format("Cache Server notified that client with ID {0} is shutting down.", this.CacheClientNodeIdentifier);
                                if (this.LogWriter != null)
                                    this.LogWriter.Write(message, this.LoggingCategory, 1, (int)LoggingEvent.CacheNotification, TraceEventType.Information, "ClientNodeGoingDown");
                            }
                        }
                        catch (DataCacheException dataCacheException)
                        {
                            shouldRetry = this.HandleCacheException(retryCount, dataCacheException);
                            retryCount++;
                            //if (shouldRetry == false)
                            //    throw;
                        }
                    } while (shouldRetry);

                }

            }
        }
    }
}
